import ch.quantasy.tinkerforge.tinker.TinkerforgeDevice;

import com.tinkerforge.BrickletAmbientLight.IlluminanceListener;
import com.tinkerforge.BrickletDistanceIR.DistanceListener;
import com.tinkerforge.BrickletHumidity.HumidityListener;
import com.tinkerforge.BrickletTemperatureIR.AmbientTemperatureListener;
import com.tinkerforge.BrickletTemperatureIR.ObjectTemperatureListener;

/**
 * This class is used to fuse the {@link FridgeSensor} and the
 * {@link FridgeViewer}.
 * 
 * @author reto
 * 
 */
public class FridgeIt {
	// The 'server'-name of the fridge-sensor-stack
	public static final String FRIDGE_SENSOR_HOST_NAME = "fridge-sensor";
	// Assumes to be connected via USB
	public static final String FRIDGE_VIEWER_HOST_NAME = "localhost";

	private final FridgeViewer fridgeViewer;
	private final FridgeSensor fridgeSensor;

	/**
	 * The {@link FridgeSensor} and the {@link FridgeViewer} are instantiated
	 * and connected. The instance will only exist if everything went fine.
	 * 
	 * @throws Exception
	 *             if one of the
	 */
	public FridgeIt() throws Exception {
		this.fridgeViewer = new FridgeViewer(FRIDGE_VIEWER_HOST_NAME);
		this.fridgeSensor = new FridgeSensor(FRIDGE_SENSOR_HOST_NAME,
				new ObjectTemperatureManager(),
				new AmbientTemperatureManager(), new HumidityManager(),
				new DoorManager(), new LightManager());
		try {
			this.fridgeViewer.connect();
			this.fridgeSensor.connect();
		} catch (final Exception ex) {
			this.stop();
			throw ex;
		}
	}

	/**
	 * This method allows to gracefully disconnect from {@link FridgeViewer} and
	 * {@link FridgeSensor}
	 */
	public void stop() {
		if (this.fridgeSensor != null) {
			this.fridgeSensor.disconnect();
		}
		if (this.fridgeViewer != null) {
			this.fridgeViewer.disconnect();
		}

	}

	/**
	 * This class is responsible for receiving, processing and delegating data
	 * about the ambient light.
	 * 
	 * @author reto
	 * 
	 */
	class LightManager implements IlluminanceListener {
		// A heuristic value representing the illuminance-threshold above which
		// the light in the fridge is lit.
		// Why is it not 0? The sensor gives some noise!
		// Why is it not 0? Old cheese might be glowing in the dark ;-)
		private static final int LIGHT_IS_SWITCHED_ON = 7;
		private Boolean isLightLit;

		/**
		 * the data is processed to the information if the interieur light of
		 * the fridge is lit or not. Only a change in this information will be
		 * delegated to the {@link FridgeViewer}
		 */
		@Override
		public void illuminance(final int illuminance) {
			final Boolean isLightLitNow = illuminance > LightManager.LIGHT_IS_SWITCHED_ON;
			if ((this.isLightLit == null) || (this.isLightLit != isLightLitNow)) {
				this.isLightLit = isLightLitNow;
				FridgeIt.this.fridgeViewer.setLight(this.isLightLit);
			}

		}

	}

	/**
	 * This class is responsible for receiving, processing and delegating data
	 * about the object temperature the IR-thermometer is facing.
	 * 
	 * @author reto
	 * 
	 */
	class ObjectTemperatureManager implements ObjectTemperatureListener {

		/**
		 * It sends all data to the {@link FridgeViewer}
		 */
		@Override
		public void objectTemperature(final short temperature) {
			FridgeIt.this.fridgeViewer.setObjectTemp(temperature);
		}

	}

	/**
	 * This class is responsible for receiving, processing and delegating data
	 * about the ambient-temperature the {@link FridgeSensor} is sensing.
	 * 
	 * @author reto
	 * 
	 */
	class AmbientTemperatureManager implements AmbientTemperatureListener {

		/**
		 * It sends all data to the {@link FridgeViewer}
		 */
		@Override
		public void ambientTemperature(final short temperature) {
			FridgeIt.this.fridgeViewer.setAmbientTemp(temperature);
		}

	}

	/**
	 * This class is responsible for receiving, processing and delegating data
	 * about the relative ambient-humidity the {@link FridgeSensor} is sensing.
	 * 
	 * @author reto
	 * 
	 */
	class HumidityManager implements HumidityListener {

		/**
		 * It sends all data to the {@link FridgeViewer}
		 */
		@Override
		public void humidity(final int humidity) {
			FridgeIt.this.fridgeViewer.setHumidity(humidity);
		}
	}

	/**
	 * This class is responsible for receiving, processing and delegating data
	 * about the door-state.
	 * 
	 * @author reto
	 * 
	 */
	class DoorManager implements DistanceListener {
		// A heuristic distance (mm) the FridgeSensor is placed from the shut
		// door.
		// To make this dynamic is left as an exercise for the reader. ;-)
		private static final int DOOR_IS_CLOSED = 150;
		Boolean isDoorOpen;

		/**
		 * the data is processed to the information if the door of the fridge is
		 * shut or not. Only a change in this information will be delegated to
		 * the {@link FridgeViewer}
		 */
		@Override
		public void distance(final int distance) {
			final boolean isDoorOpenNow = distance > DoorManager.DOOR_IS_CLOSED;
			if ((this.isDoorOpen == null) || (this.isDoorOpen != isDoorOpenNow)) {
				this.isDoorOpen = isDoorOpenNow;
				FridgeIt.this.fridgeViewer.setDoor(this.isDoorOpen);
			}

		}

	}

	/**
	 * A simple boot-strap. The program will shut-down gracefully if one hits
	 * 'any' key on the console
	 * 
	 * @param args
	 * @throws Exception
	 */
	public static void main(final String[] args) throws Exception {
		FridgeIt fridgeIt = null;
		try {
			fridgeIt = new FridgeIt();
			System.in.read();
			fridgeIt.stop();
		} catch (final Exception ex) {
			ex.printStackTrace();
		}
	}
}
